/*
PearReview - The peer review assistant.
Copyright (C) 2009  Dimitrij Pankratz, Anton Musichin
http://www.pearreview.com, info@pearreview.com

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

package com.pearreview.ui.tree;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;

import javax.swing.event.EventListenerList;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;

import com.pearreview.util.review.Aspect;
import com.pearreview.util.review.Aspects;
import com.pearreview.util.review.Catalog;
import com.pearreview.util.review.IdLinkedList;
import com.pearreview.util.review.IdList;

public final class CatalogTreeModel implements TreeModel, Serializable {
	
	private static final long serialVersionUID = 3498167665581544543L;
	
	private EventListenerList treeModelListener = new EventListenerList();
	private DefaultTreeNode root;
	
	/**
	 * Creates a new tree with empty model, use <code>set</code> 
	 * methods to set model.
	 */
	public CatalogTreeModel() {
	}
	
	/**
	 * Creates a new Catalog tree.
	 * You can not manage only aspects in this mode.
	 * @param aspects aspects to add to the tree
	 * @param rootName root name of the aspects
	 */
	public CatalogTreeModel(Catalog[] catalogs, String[] names, 
			String rootName) {
		this();
		setCatalogs(catalogs, names, rootName);
	}
	
	/**
	 * Creates a new Aspects tree.
	 * You can not manage catalogs in this mode.
	 * @param aspects aspects to add to the tree
	 * @param rootName root name of the aspects
	 */
	public CatalogTreeModel(IdList<Aspect> aspects, String rootName) {
		this();
		setAspects(aspects, rootName);
	}
	
	/**
	 * Creates a new Catalog tree.
	 * You can not manage only aspects in this mode.
	 * @param aspects aspects to add to the tree
	 * @param rootName root name of the aspects
	 */
	public CatalogTreeModel(IdList<Aspect>[] aspectsList, String[] names, 
			String rootName) {
		this();
		setAspectsList(aspectsList, names, rootName);
	}
	
	/**
	 * Creates new empty aspects root
	 * @param rootName
	 */
	public void createEmptyAspectsRoot(String rootName) {
		setAspectsList(null, null, rootName);
	}
	
	/**
	 * Creates new empty catalog root
	 * @param rootName
	 */
	public void createEmptyCatalogRoot(String rootName) {
		setCatalogs(null, null, rootName);
	}
	
	/**
	 * Creates new empty aspects node
	 * @param rootName
	 */
	public void createEmptyAspectsNode(String rootName) {
		setAspects(null, rootName);
	}
	
	/**
	 * Sets a new Catalog tree.
	 * You can not manage only aspects in this mode.
	 * @param aspects aspects to add to the tree
	 * @param rootName root name of the aspects
	 */
	public void setAspectsList(IdList<Aspect>[] aspectsList, String[] names, 
			String rootName) {
		TreeModelEvent event;
		TreeNode oldRoot = root;
		
		/* create new root */
		AspectsRoot node = new AspectsRoot();
		node.aspectsData = aspectsList;
		node.namesData = names;
		node.nameData = rootName;
		root = node;
		
		/* fire event */
		if(oldRoot == null) {
			event = new TreeModelEvent(this, (TreePath)null);
		} else {
			event = new TreeModelEvent(this, new TreePath(oldRoot));
		}
		fireStructureChanged(event);
	}
	
	/**
	 * Sets a new Catalog tree.
	 * You can not manage only aspects in this mode.
	 * @param aspects aspects to add to the tree
	 * @param rootName root name of the aspects
	 */
	public void setCatalogs(Catalog[] catalogs, String[] names, 
			String rootName) {
		TreeModelEvent event;
		TreeNode oldRoot = root;
		
		/* create new root */
		CatalogRoot node = new CatalogRoot();
		node.catalogsData = catalogs;
		node.namesData = names;
		node.nameData = rootName;
		root = node;
		
		/* fire event */
		if(oldRoot == null) {
			event = new TreeModelEvent(this, (TreePath)null);
		} else {
			event = new TreeModelEvent(this, new TreePath(oldRoot));
		}
		fireStructureChanged(event);
	}
	
	/**
	 * Gets catalogs and catalog names of this model and store it
	 * in the given array.
	 * @param catalogs store catalogs to this array
	 * @param names store catalog names to this array
	 * @return the count of catalogs, where stored to arrays.
	 * 
	 * @throws NullPointerException if a on or more given array is 
	 * <code>null</code>
	 * 
	 * @throws IndexOutOfBoundsException if one or more arrays are to small
	 * to store catalogs
	 */
	public int getCatalogs(Catalog[] catalogs, String[] names) 
			throws NullPointerException, IndexOutOfBoundsException {
		int count = getCatalogCount();
		
		if(count <= 0)
			return 0;
		
		if(catalogs == null || names == null) {
			throw new NullPointerException();
		}
		
		Catalog[] rootCatalogs = getCatalogs();
		String[] rootNames = getCatalogNames();
		
		for(int i = 0 ; i < count ; i++ ) {
			catalogs[i] = rootCatalogs[i];
			names[i] = rootNames[i];
		}
		
		return count;
		
	}
	
	/**
	 * Gets catalogs of this model
	 * @return catalogs
	 */
	public Catalog[] getCatalogs() {
		
		if(!(root instanceof CatalogRoot))
			return null;
		
		TreeNode cr = (CatalogRoot)root;
		
		int count = cr.getChildCount();
		Catalog[] catalogs = new Catalog[count];
		
		for(int i = 0 ; i < count ; i++)
			catalogs[i] = getCatalog(i);
		
		return catalogs;
	}
	
	public Catalog getCatalog(int i) {
		if(!(root instanceof CatalogRoot))
			return null;
		
		CatalogNode cn = (CatalogNode) root.getChildAt(i);
		Catalog catalog = new Catalog();
		catalog.setDescription(cn.catalog.getDescription());
		if(catalog.getDescription() == null)
			catalog.setDescription("");
		
		IdList<Aspect> lst = new IdLinkedList<Aspect>();
		cn.generateAspectListOfNodes(lst);
		lst.regenerateIds();
		
		catalog.setAspects(lst);
		
		return catalog;
	}
	
	public String getCatalogName(int i) {
		if(!(root instanceof CatalogRoot))
			return null;
		
		CatalogNode cn = (CatalogNode) root.getChildAt(i);
		return cn.getName();
	}
	
	/**
	 * Gets names of catalogs
	 * @return catalog names
	 */
	public String[] getCatalogNames() {
		if(root instanceof CatalogRoot) {
			String[] names = new String[root.getChildCount()];
			for(int i = 0 ;  i < names.length ; i++) {
				names[i] = getCatalogName(i);
			}
			
			return names;
		}
		return null;
	}
	
	/**
	 * Gets count of catalogs
	 * @return catalog count
	 */
	public int getCatalogCount() {
		if(root instanceof CatalogRoot) {
			return root.getChildCount();
		}
		return 0;
	}
	
	/**
	 * Gets aspects list of this model
	 * @return catalogs
	 */
	@SuppressWarnings("unchecked")
	public IdList<Aspect>[] getAspectsList() {
		
		if(!(root instanceof AspectsRoot))
			return null;
		
		TreeNode ar = root;
		
		int count = ar.getChildCount();
		IdList<Aspect>[] aspectsList = new IdList[count];
		
		for(int i = 0 ; i < count ; i++) {
			AspectsNode an = (AspectsNode) root.getChildAt(i);
			aspectsList[i] = new IdLinkedList<Aspect>();
			an.generateAspectListOfNodes(aspectsList[i]);
			aspectsList[i].regenerateIds();
		}
		
		return aspectsList;
	}
	
	/**
	 * Gets count of aspects list
	 * @return catalog count
	 */
	public int getAspectsListsCount() {
		if(root instanceof AspectsRoot) {
			return root.getChildCount();
		}
		return 0;
	}
	
	/**
	 * Sets a new Aspects tree.
	 * You can not manage catalogs in this mode.
	 * @param aspects aspects to add to the tree
	 * @param rootName root name of the aspects
	 */
	public void setAspects(IdList<Aspect> aspects, String rootName) {
		TreeModelEvent event;
		TreeNode oldRoot = root;
		
		/* create new root */
		AspectsNode node = new AspectsNode();
		node.nameData = rootName;
		node.aspects = aspects;
		root = node;
		
		/* fire event */
		if(oldRoot == null) {
			event = new TreeModelEvent(this, (TreePath)null);
		} else {
			event = new TreeModelEvent(this, new TreePath(oldRoot));
		}
		fireStructureChanged(event);
	}
	
	public void removeChildren(TreePath path) {
		Object node = path.getLastPathComponent();
		if(node instanceof CatalogOrAspectsNode) {
			CatalogOrAspectsNode coan = (CatalogOrAspectsNode) node;
			
			int childCount = coan.getChildCount();
			Object[] children = new Object[childCount];
			int[] childIndices = new int[childCount];
			for(int i = 0 ; i < childCount ; i++) {
				childIndices[i] = i;
				children[i] = coan.getChildAt(i);
			}
			
			coan.removeAllChildren();
			
			/* fire event */
			TreeModelEvent event = new TreeModelEvent(this, 
					path,
					childIndices,
					children);
			fireNodesRemoved(event);
		}
	}
	
	/**
	 * Gets aspects of this model
	 * @return aspects
	 */
	public IdList<Aspect> getAspects() {
		if(!(root instanceof AspectsNode))
			return null;
		
		AspectsNode an = (AspectsNode)root;
		
		return getAspectsOfAspectsNode(an);
	}
	
	public IdList<Aspect> getAspectsOfAspectsNode(AspectsTreeNode an) {
		if(!(an instanceof AspectsNode))
			return null;
		
		IdList<Aspect> lst = new IdLinkedList<Aspect>();
		
		((AspectsNode)an).generateAspectListOfNodes(lst);
		lst.regenerateIds();
		
		return lst;
	}
	
	@Override
	public void valueForPathChanged(TreePath path, Object newValue) {		
		editNode(path, newValue);
	}
	
	/**
	 * Removes lower nodes.
	 * @param paths
	 * @return
	 */
	public static TreePath[] removeLowerNodes(TreePath[] paths) {
		List<TreePath> lPaths = new ArrayList<TreePath>(Arrays.asList(paths));
		// any optimization?!
		for(int i = 0 ; i < lPaths.size() ; i++) {
			for(int j = 0 ; j < lPaths.size() ; j++) {
				if(i == j)
					continue;
				if(lPaths.get(j).isDescendant(lPaths.get(i))) {
					lPaths.remove(i);
					i--;
					break;
				}
			}
		}
		
		TreePath[] p = new TreePath[lPaths.size()];
		lPaths.toArray(p);
		
		return p;
	}
	
	/**
	 * Returns a tree path of node
	 * @param node specified node
	 * @return node tree path
	 */
	public static TreePath getTreePathOfNode(TreeNode node) {
		List<Object> lst = new LinkedList<Object>();
		
		while(node != null) {
			lst.add(node);
			node = node.getParent();
		}

		Object[] oPath = new Object[lst.size()];
		for(int i = lst.size() - 1 ; i >= 0 ; i--) {
			oPath[lst.size() - i - 1] = lst.get(i);
		}
		
		return new TreePath(oPath);
	}
	
	/**
	 * Gets node level. Results:<br>
	 * 0: non<br>
	 * 1: CatalogRootTreeNode, AspectsRootTreNode<br>
	 * 2: CatalogTreeNode, AspectsTreeNode<br>
	 * 3: CategoryTreeNode<br>
	 * 4: AspectTreeNode<br>
	 * Integer.Max_VALUE: unknown
	 * @param path path to check
	 */
	public static int getNodeLevel(TreePath path) {
		int lvl = 0;
		
		Object node = path.getLastPathComponent();
		if(node instanceof AspectTreeNode)
			lvl =  4;
		else if(node instanceof CategoryTreeNode)
			lvl = 3;
		else if(node instanceof CatalogTreeNode 
				|| node instanceof AspectsTreeNode)
			lvl = 2;
		else if(node instanceof CatalogRootTreeNode
				|| node instanceof AspectsRootTreeNode)
			lvl = 1;
		else
			lvl = Integer.MAX_VALUE;
		
		return lvl;
	}
	
	/**
	 * Gets upper node level. Results:<br>
	 * 0: non<br>
	 * 1: CatalogRootTreeNode, AspectsRootTreNode<br>
	 * 2: CatalogTreeNode, AspectsTreeNode<br>
	 * 3: CategoryTreeNode<br>
	 * 4: AspectTreeNode<br>
	 * Integer.Max_VALUE: unknown
	 * @param paths paths to check
	 */
	public static int getUpperNodeLevel(TreePath[] paths) {
		if(paths == null || paths.length == 0)
			return 0;
		
		int upper = Integer.MAX_VALUE;
		int current;
		for(TreePath path: paths) {
			current = getNodeLevel(path);
			upper = Math.min(upper, current);
		}
		
		return upper;
	}
	
	/**
	 * Edits name and description of an aspect node
	 * @param path path to the aspect node to edit
	 * @param directive new directive
	 * @param description new description
	 * 
	 * @throws IllegalArgumentException if path doesn't 
	 * contains at last component an aspect node
	 */
	public void editAspectNode(TreePath path, String directive, 
			String description) throws IllegalArgumentException {
		
		if(path.getLastPathComponent() instanceof AspectNode) {		
			Object[] obj = {directive, description};
			editNode(path, obj);
		} else {
			throw new IllegalArgumentException();
		}
	}
	
	/**
	 * Edits name of a category node
	 * @param path path to the category node to edit
	 * @param name new category name
	 * 
	 * @throws IllegalArgumentException if path doesn't 
	 * contains at last component an category node
	 */
	public void editCategoryNode(TreePath path, String name) 
			throws IllegalArgumentException{
		if(path.getLastPathComponent() instanceof CategoryNode) {		
			editNode(path, name);
		} else {
			throw new IllegalArgumentException();
		}
	}
	
	/**
	 * Edits name and description of a catalog node
	 * @param path path to the catalog node to edit
	 * @param name new catalog name
	 * @param name new catalog description
	 * 
	 * @throws IllegalArgumentException if path doesn't 
	 * contains at last component an category node
	 */
	public void editCatalogNode(TreePath path, String name, 
			String description) throws IllegalArgumentException {
		
		if(path.getLastPathComponent() instanceof CatalogNode) {		
			Object[] obj = {name, description};
			editNode(path, obj);
		} else {
			throw new IllegalArgumentException();
		}
	}
	
	/**
	 * Edits name of a aspects node
	 * @param path path to the aspects node to edit
	 * @param name new aspects name
	 * 
	 * @throws IllegalArgumentException if path doesn't 
	 * contains at last component an category node
	 */
	public void editAspectsNode(TreePath path, String name) 
			throws IllegalArgumentException {
		
		if(path.getLastPathComponent() instanceof AspectsNode) {		
			editNode(path, name);
		} else {
			throw new IllegalArgumentException();
		}
	}
	
	/**
	 * Edits name of a catalog root
	 * @param path path to the catalog root to edit
	 * @param name new catalog root name
	 * 
	 * @throws IllegalArgumentException if path doesn't 
	 * contains at last component an category node
	 */
	public void editCatalogRoot(TreePath path, String name) 
			throws IllegalArgumentException {
		
		if(path.getLastPathComponent() instanceof CatalogRoot) {		
			editNode(path, name);
		} else {
			throw new IllegalArgumentException();
		}
	}
	
	/**
	 * Edits name of a aspects root
	 * @param path path to the aspects root to edit
	 * @param name new aspects root name
	 * 
	 * @throws IllegalArgumentException if path doesn't 
	 * contains at last component an category node
	 */
	public void editAspectsRoot(TreePath path, String name) 
			throws IllegalArgumentException {
		
		if(path.getLastPathComponent() instanceof AspectsRoot) {		
			editNode(path, name);
		} else {
			throw new IllegalArgumentException();
		}
	}
	
	/**
	 * Edits name and if applicable the description of a node.
	 * @param path path to the node
	 * @param value value to set
	 */
	public void editNode(TreePath path, Object value) {
		/* aspect changed */
		if(path.getLastPathComponent() instanceof AspectNode) {
			AspectNode node = (AspectNode)path.getLastPathComponent();
			
			String description = null;
			String directive = null;
			
			if(value instanceof Aspect) {
				Aspect aspect = (Aspect)value;
				description = aspect.getDescription();
				directive = aspect.getDirective();
			} else if(value instanceof AspectTreeNode) {
				AspectTreeNode atn = (AspectTreeNode)value;
				description = atn.getAspect().getDescription();
				directive = atn.getAspect().getDirective();
			} else if(value instanceof Object[]) {
				Object[] obj = (Object[])value;
				if(obj.length > 0 && obj[1] != null)
					description = obj[1].toString();
				if(obj.length >= 0 && obj[0] != null)
					directive = obj[0].toString();
			} else {
				description = node.getAspect().getDescription();
				directive = value.toString();
			}
			
			/* apply changes */
			node.aspectData.setDirective(directive);
			node.aspectData.setDescription(description);
			
			/* fire event */
			TreeModelEvent event = new TreeModelEvent(node, path);
			fireNodesChanged(event);
		/* category changed */
		} else if (path.getLastPathComponent() instanceof CategoryNode) {
			CategoryNode node = (CategoryNode)path.getLastPathComponent();
			
			String category;
			
			if(value instanceof Aspect) {
				Aspect aspect = (Aspect)value;
				category = aspect.getCategory();
			} else if(value instanceof CategoryTreeNode) {
				CategoryTreeNode ctn = (CategoryTreeNode)value;
				category = ctn.getName();
			} else if(value instanceof Object[]) {
				Object[] obj = (Object[])value;
				category = obj[0].toString();
			} else {
				category = value.toString();
			}
			
			/* apply changes */
			int childCount = node.getChildCount();
			for(int i = 0 ; i < childCount ; i++ ) {
				AspectNode ch = node.getChildAt(i);
				ch.getAspect().setCategory(category);
			}
			node.nameData = category;
			
			/* fire event */
			TreeModelEvent event = new TreeModelEvent(node, path);
			fireNodesChanged(event);
		/* catalog changed */
		} else if(path.getLastPathComponent() instanceof CatalogOrAspectsNode){
			CatalogOrAspectsNode node = 
					(CatalogOrAspectsNode)path.getLastPathComponent();
			
			String name;
			String description = null;
			
			if(value instanceof AspectsTreeNode) {
				AspectsTreeNode atn = (AspectsTreeNode)value;
				name = atn.getName();
			} else if(value instanceof CatalogTreeNode) {
				CatalogTreeNode ctn = (CatalogTreeNode)value;
				name = ctn.getName();
				if(ctn.getCatalog() != null)
					description = ctn.getCatalog().getDescription();
			} else if(value instanceof Object[]) {
				Object[] obj = (Object[])value;
				name = obj[0].toString();
				if(obj.length > 1)
					description = obj[1].toString();
			} else {
				name = value.toString();
			}
			
			/* apply changes */
			node.nameData = name;
			
			if(node instanceof CatalogNode && description != null) {
				CatalogNode cn = (CatalogNode)node;
				if(cn.getCatalog() != null)
					cn.getCatalog().setDescription(description);
			}
			
			/* apply changes by parent */
			TreeNode parent = node.getParent();
			
			if(parent instanceof CatalogOrAspectsRoot) {
				CatalogOrAspectsRoot coaRoot = (CatalogOrAspectsRoot)parent;
				int index = coaRoot.getIndex(node);
				if(index >= 0 && index < coaRoot.namesData.length) {
					coaRoot.namesData[index] = name;
				}
				
			}
			
			/* fire event */
			TreeModelEvent event = new TreeModelEvent(node, path);
			fireNodesChanged(event);
		} else if(path.getLastPathComponent() instanceof CatalogOrAspectsRoot) {
			CatalogOrAspectsRoot coaRoot = 
					(CatalogOrAspectsRoot)path.getLastPathComponent();
			
			String name = value.toString();
			
			/* apply changes */
			coaRoot.nameData = name;
			
			/* fire event */
			TreeModelEvent event = new TreeModelEvent(coaRoot, path);
			fireNodesChanged(event);
		}
	}
	
	/**
	 * Removes a node.
	 * @param path path to the node
	 */
	public void removeNode(TreePath path) {
		TreeNode node;
		if(path.getLastPathComponent() instanceof TreeNode)
			node = (TreeNode)path.getLastPathComponent();
		else
			return;
		
		if(node instanceof AspectNode) {
			AspectNode aspectNode = (AspectNode) node;
			if(node.getParent() instanceof CategoryNode) {
				CategoryNode categoryNode = (CategoryNode)node.getParent();
				
				int index = categoryNode.getIndex(aspectNode);
				
				/* remove */				
				categoryNode.removeNode(index);

				/* remove from aspects or catalog - model */
				if(aspectNode.aspects != null) {
					aspectNode.aspects.remove(aspectNode.aspectData);
				} else if(aspectNode.catalog != null) {
					aspectNode.catalog.getAspects()
							.remove(aspectNode.aspectData);
				}
				
				/* fire event */
				Object[] children = {aspectNode};
				int[] childIndices = {index};
				TreeModelEvent event = new TreeModelEvent(this, 
						path.getParentPath(),
						childIndices,
						children);
				fireNodesRemoved(event);
			} else if(node.getParent() instanceof CatalogOrAspectsNode) {
				CatalogOrAspectsNode coaNode = 
						(CatalogOrAspectsNode)node.getParent();
				
				int index = coaNode.getIndex(aspectNode);
				
				/* remove */				
				coaNode.removeNode(index);
				
				/* remove from aspects or catalog - model */
				if(aspectNode.aspects != null) {
					aspectNode.aspects.remove(aspectNode.aspectData);
				} else if(aspectNode.catalog != null) {
					aspectNode.catalog.getAspects()
							.remove(aspectNode.aspectData);
				}
				
				/* fire event */
				Object[] children = {aspectNode};
				int[] childIndices = {index};
				TreeModelEvent event = new TreeModelEvent(this, 
						path.getParentPath(),
						childIndices,
						children);
				fireNodesRemoved(event);
			}
		} else if(node instanceof CategoryNode) {
			CatalogOrAspectsNode coaNode = 
					(CatalogOrAspectsNode)node.getParent();
			
			int index = coaNode.getIndex(node);
			
			/* remove */				
			coaNode.removeNode(index);
			
			/* remove from aspects or catalog - model */
			CategoryNode categoryNode = (CategoryNode)node;
			String categoryToRemove = categoryNode.nameData;
			if(categoryNode.aspects != null) {
				Aspect[] aspectsToRemove = 
						Aspects.getCategoryAspects(categoryNode.aspects, 
								categoryToRemove);
				categoryNode.aspects
						.removeAll(Arrays.asList(aspectsToRemove));
			} else if(categoryNode.catalog != null) {
				Aspect[] aspectsToRemove = 
						Aspects.getCategoryAspects(categoryNode.catalog
								.getAspects(), categoryToRemove);
				categoryNode.catalog.getAspects()
						.removeAll(Arrays.asList(aspectsToRemove));
			}
			
			/* fire event */
			Object[] children = {node};
			int[] childIndices = {index};
			TreeModelEvent event = new TreeModelEvent(this, 
					path.getParentPath(),
					childIndices,
					children);
			fireNodesRemoved(event);
		} else if(node instanceof CatalogOrAspectsNode) {
			CatalogOrAspectsRoot coaRoot = 
					(CatalogOrAspectsRoot)node.getParent();

			/* its a root */
			if(coaRoot == null) {
				fireStructureChanged(new TreeModelEvent(this, path));
			} else {
			
			int index = coaRoot.getIndex(node);
				
				/* remove */				
				coaRoot.removeNode(index);
				
				/* remove from aspects or catalog - model */
				// not necessary
				
				/* fire event */
				Object[] children = {node};
				int[] childIndices = {index};
				TreeModelEvent event = new TreeModelEvent(this, 
						path.getParentPath(),
						childIndices,
						children);
				fireNodesRemoved(event);
			}
		}
	}
	
	/**
	 * Adds a list of aspect to root.
	 * @param aspects aspects to add
	 * @param name name of aspects
	 * @return tree path to the aspects node
	 * 
	 * @throws NullPointerException if root element is <code>null</code>
	 */
	public TreePath addAspectsNode(TreePath path, IdList<Aspect> aspects, 
			String name) {
		return insertAspectsNode(path, aspects, name, -1);
	}
	
	/**
	 * Adds a new empty list of aspects with specific name to root.
	 * @param name name of aspects
	 * @return tree path to the aspects node
	 * 
	 * @throws NullPointerException if root element is <code>null</code>
	 */
	public TreePath addAspectsNode(TreePath path, String name) {
		return insertAspectsNode(path, name, -1);
	}
	
	/**
	 * Inserts a new empty aspects list with specific name to root.
	 * @param name name of aspects
	 * @param index index to insert
	 * @return tree path to the aspects node
	 * 
	 * @throws NullPointerException if root element is <code>null</code>
	 */
	public TreePath insertAspectsNode(TreePath path, String name, int index) {
		IdList<Aspect> lst =  new IdLinkedList<Aspect>();
		return insertAspectsNode(path, lst, name, index);
	}
	
	/**
	 * Inserts a list of aspects to root.
	 * @param aspects to add
	 * @param name name of aspects
	 * @return tree path to the aspects node
	 * 
	 * @throws NullPointerException if root element is <code>null</code>
	 */
	public TreePath insertAspectsNode(TreePath path, IdList<Aspect> aspects, 
			String name, int index){
		
		Object node = path.getLastPathComponent();
		if(node instanceof AspectsRoot) {
			AspectsRoot aspectsRoot = (AspectsRoot)node;
			
			/* insert to node */
			AspectsNode aspectsNode = 
					aspectsRoot.insertAspectsNode(aspects, name, index);
			int insertedAt = aspectsRoot.getIndex(aspectsNode);
			TreePath aspectsPath = path.pathByAddingChild(aspectsNode);
			
			/* fire event */			
			Object[] children = {aspectsNode};
			int[] childIndices = {insertedAt};
			TreeModelEvent event = new TreeModelEvent(this, 
					path,
					childIndices,
					children);
			fireNodesInserted(event);
			
			return aspectsPath;
		}
		
		throw new NullPointerException();
	}
	
	/**
	 * Adds a catalog to root.
	 * @param path adds catalog to this path
	 * @param catalog catalog to add
	 * @param name name of catalog
	 * @return tree path to the catalog
	 * 
	 * @throws NullPointerException if root element is <code>null</code>
	 */
	public TreePath addCatalogNode(TreePath path, Catalog catalog, 
			String name) {
		return insertCatalogNode(path, catalog, name, -1);
	}
	
	/**
	 * Adds a new empty catalog with specific name to root.
	 * @param path adds catalog to this path
	 * @param name name of catalog
	 * @param description description of new catalog
	 * 
	 * @return tree path to the catalog
	 * 
	 * @throws NullPointerException if root element is <code>null</code>
	 */
	public TreePath addCatalogNode(TreePath path, String name, 
			String description) {
		return insertCatalogNode(path, name, description, -1);
	}
	
	/**
	 * Inserts a new empty catalog with specific name to root.
	 * @param path adds catalog to this path
	 * @param name name of catalog
	 * @param index index to insert
	 * @param description description of new catalog
	 * 
	 * @return tree path to the catalog
	 * 
	 * @throws NullPointerException if root element is <code>null</code>
	 */
	public TreePath insertCatalogNode(TreePath path, String name, 
			String description, int index) {
		Catalog catalog = new Catalog();
		catalog.setDescription(description);
		catalog.setAspects(new IdLinkedList<Aspect>());
		
		return insertCatalogNode(path, catalog, name, index);
	}
	
	/**
	 * Inserts a catalog to root.
	 * @param path adds catalog to this path
	 * @param catalog catalog to add
	 * @param name name of catalog
	 * @return tree path to the catalog
	 * 
	 * @throws NullPointerException if root element is <code>null</code>
	 */
	public TreePath insertCatalogNode(TreePath path, Catalog catalog, 
			String name, int index){
		if(path.getLastPathComponent() instanceof CatalogRoot) {
			CatalogRoot catalogRoot = (CatalogRoot)path.getLastPathComponent();
			
			/* insert to node */
			CatalogNode catalogNode = 
					catalogRoot.insertCatalogNode(catalog, name, index);
			int insertedAt = catalogRoot.getIndex(catalogNode);
			TreePath catalogPath = path.pathByAddingChild(catalogNode);
			
			/* fire event */
			
			Object[] children = {catalogNode};
			int[] childIndices = {insertedAt};
			TreeModelEvent event = new TreeModelEvent(this, 
					path,
					childIndices,
					children);
			fireNodesInserted(event);
			
			return catalogPath;
		}
		
		throw new NullPointerException();
	}
	
	/**
	 * Adds a category node to path last position. 
	 * The node will be added at last position.
	 * @param path adds category to this path
	 * @param category category name to add
	 * @return tree path to the new category
	 * 
	 * @throws IllegalArgumentException if path doesn't 
	 * contains at last component an node, where can hold a category
	 */
	public TreePath addCategoryNode(TreePath path, String category) {
		return this.insertCategoryNode(path, category, -1);
	}
	
	/**
	 * Inserts a category node to path last position
	 * @param path adds category to this path
	 * @param category category name to add
	 * @param index index of position to insert
	 * @return tree path to the new category
	 * 
	 * @throws IllegalArgumentException if path doesn't 
	 * contains at last component an node, where can hold a category
	 */
	public TreePath insertCategoryNode(TreePath path, 
			String category, int index) {
		
		if(path.getLastPathComponent() instanceof CatalogOrAspectsNode) {
			CatalogOrAspectsNode node = 
					(CatalogOrAspectsNode)path.getLastPathComponent();
			
			/* insert */
			CategoryNode categoryNode =
					node.insertCategoryNode(category, index);
			int insertedAt = node.getIndex(categoryNode);
			TreePath categoryPath = path.pathByAddingChild(categoryNode);
			
			/* fire event */
			Object[] children = {categoryNode};
			int[] childIndices = {insertedAt};
			TreeModelEvent event = new TreeModelEvent(this, 
					path,
					childIndices,
					children);
			fireNodesInserted(event);
			
			return categoryPath;
			
		}
		
		throw new IllegalArgumentException();
	}
	
	
	/**
	 * Adds an aspect node to the path at last position
	 * @param path adds a node to this path
	 * @param direcive directive of an aspect
	 * @param description description of an aspect
	 * 
	 * @return path to the inserted node
	 * 
	 * @throws IllegalArgumentException if path doesn't 
	 * contains at last component an node, where can hold an aspect
	 */
	public TreePath addAspectNode(TreePath path, 
			String directive, String description) {
		return insertAspectNode(path, directive, description, -1);
	}
	
	/**
	 * Adds an aspect node to the path
	 * @param path adds a node to this path
	 * @param aspectToAdd An aspect to add to tree
	 * 
	 * @return path to the inserted node
	 * 
	 * @throws IllegalArgumentException if path doesn't 
	 * contains at last component an node, where can hold an aspect
	 */
	public TreePath addAspectNode(TreePath path, Aspect aspectToAdd) {
		return insertAspectNode(path, aspectToAdd, -1);
	}
	
	/**
	 * Inserts an aspect node to the path
	 * @param path adds a node to this path
	 * @param direcive directive of an aspect
	 * @param description description of an aspect
	 * 
	 * @return path to the inserted node
	 * 
	 * @throws IllegalArgumentException if path doesn't 
	 * contains at last component an node, where can hold an aspect
	 */
	public TreePath insertAspectNode(TreePath path, 
			String directive, String description, int index) {
		
		Aspect aspectToAdd = new Aspect();
		aspectToAdd.setDirective(directive);
		aspectToAdd.setDescription(description);
		
		return insertAspectNode(path, aspectToAdd, index);
	}
	
	/**
	 * Inserts an aspect node to the path
	 * @param path adds a node to this path
	 * @param aspectToAdd An aspect to add to tree
	 * 
	 * @return path to the inserted node
	 * 
	 * @throws IllegalArgumentException if path doesn't 
	 * contains at last component an node, where can hold an aspect
	 */
	public TreePath insertAspectNode(TreePath path, 
			Aspect aspectToAdd, int index) {
		if(path.getLastPathComponent() instanceof TreeNode) {
			TreeNode node = (TreeNode)path.getLastPathComponent();
			
			if(node instanceof CategoryNode) {
				CategoryNode categoryNode = (CategoryNode)node;
				
				aspectToAdd.setCategory(categoryNode.nameData);
				
				/* insert to the node */
				TreeNode aspectNode = 
						categoryNode.insertAspectNode(aspectToAdd, index);
				int insertedAt = categoryNode.getIndex(aspectNode);
				TreePath aspectPath = path.pathByAddingChild(aspectNode);
				
				/* adds to the aspects or catalog model */
				if(categoryNode.aspects != null) {
					categoryNode.aspects.add(aspectToAdd);
				} else if(categoryNode.catalog != null) {
					categoryNode.catalog.getAspects().add(aspectToAdd);
				}
				
				/* fire event */				
				Object[] children = {aspectNode};
				int[] childIndices = {insertedAt};
				TreeModelEvent event = new TreeModelEvent(this, 
						path,
						childIndices,
						children);
				fireNodesInserted(event);
				
				return aspectPath;
			} else if(node instanceof CatalogOrAspectsNode) {
				CatalogOrAspectsNode coaNode = (CatalogOrAspectsNode)node;

				/* insert to the node */
				TreeNode aspectNode = 
						coaNode.insertAspectNode(aspectToAdd, index);
				int insertedAt = coaNode.getIndex(aspectNode);
				TreePath aspectPath = path.pathByAddingChild(aspectNode);
				
				/* adds to the aspects or catalog model */
				if(coaNode.aspects != null) {
					coaNode.aspects.add(aspectToAdd);
				} else if(coaNode.catalog != null) {
					coaNode.catalog.getAspects().add(aspectToAdd);
				}
				
				/* fire event */				
				Object[] children = {aspectNode};
				int[] childIndices = {insertedAt};
				TreeModelEvent event = new TreeModelEvent(this, 
						path,
						childIndices,
						children);
				fireNodesInserted(event);
				
				return aspectPath;
			}
		}
		
		throw new IllegalArgumentException();
	}

	@Override
	public void addTreeModelListener(TreeModelListener l) {
		treeModelListener.add(TreeModelListener.class, l);
	}

	@Override
	public void removeTreeModelListener(TreeModelListener l) {
		treeModelListener.remove(TreeModelListener.class, l);
	}
	
	/**
	 * Checks if two nodes can be swapped
	 * @param target
	 * @param sources
	 */
	public boolean canMoveNodes(TreePath[] sources, TreePath target) {
		if(sources == null)
			return false;
		
		for(TreePath source: sources) {
			if(!canMoveNode(target, source))
				return false;
		}
		
		return true;
	}
	
	/**
	 * Checks if two nodes can be swapped
	 * @param target path 1
	 * @param source path 2
	 * @return <code>true</code> if nodes could be swapped, otherwise
	 * <code>false</code>
	 */
	public boolean canMoveNode(TreePath source, TreePath target) {
		if(target == null || source == null)
			return false;
		
		if(target.equals(source))
			return true;

		if(!(target.getLastPathComponent() instanceof TreeNode) 
				|| !(source.getLastPathComponent() instanceof TreeNode))
			return false;

		TreeNode targetNode = (TreeNode)target.getLastPathComponent();
		TreeNode sourceNode = (TreeNode)source.getLastPathComponent();

		if(targetNode.getParent() == null || sourceNode.getParent() == null)
			return false;
		
		if(target.equals(source))
			return false;

		if(targetNode.getParent().equals(sourceNode.getParent())) {
			if(targetNode instanceof AspectTreeNode
					&& sourceNode instanceof AspectTreeNode)
				return true;
			if(targetNode instanceof CategoryTreeNode
					&& sourceNode instanceof CategoryTreeNode)
				return true;
			if(targetNode instanceof CatalogTreeNode
					&& sourceNode instanceof CatalogTreeNode)
				return true;
			if(targetNode instanceof AspectsTreeNode
					&& sourceNode instanceof AspectsTreeNode)
				return true;
			if(targetNode instanceof CatalogRootTreeNode
					&& sourceNode instanceof CatalogRootTreeNode)
				return true;
			if(targetNode instanceof AspectsRootTreeNode
					&& sourceNode instanceof AspectsRootTreeNode)
				return true;
		}
		
		return false;
	}
	
	/**
	 * Checks if two nodes can be swapped
	 * @param target
	 * @param sources
	 */
	public TreePath[] moveNodes(TreePath[] sources, TreePath target) {
		if(sources == null)
			return null;
		
		TreePath paths[] = new TreePath[sources.length];
		TreePath addPath = target;
		for(int i = 0 ;  i < sources.length ; i++) {
			paths[i] = moveNode(sources[i], addPath);
			addPath = paths[i];
		}
		
		return paths;
	}
	
	/**
	 * Swaps two nodes. <br> 
	 * NOTE: check of possibility with <code>canSwapNodes</code>-method.
	 * @param source
	 * @param target
	 * 
	 * @throws IllegalArgumentException if nodes could not be swapped.
	 */
	public TreePath moveNode(TreePath source, TreePath target) {
		if(!canMoveNode(source, target))
			throw new IllegalArgumentException();
		
		TreeNode sourceNode = (TreeNode)source.getLastPathComponent();
		TreeNode targetNode = (TreeNode)target.getLastPathComponent();
		
		Node parentNode = (Node)targetNode.getParent();
		int index0 = parentNode.getIndex(sourceNode);
		int index1 = parentNode.getIndex(targetNode);
		parentNode.moveNode(index0, index1);
		
		/* prepare events */
		int minIndex = Math.min(index0, index1);
		int maxIndex = Math.max(index0, index1);
		int count = maxIndex - minIndex + 1;
		Object[] children = new Object[count];
		int[] childIndices = new int[count];
		for(int i = minIndex ; i <= maxIndex ; i++) {
			childIndices[i-minIndex] = i;
			children[i-minIndex] = parentNode.getChildAt(i);
		}
		TreeModelEvent event = new TreeModelEvent(this, 
				source.getParentPath(),
				childIndices,
				children);
		/* fire delete event */
		fireNodesRemoved(event);
		
		/* fire inserted event */
		fireNodesInserted(event);
		
		return source.getParentPath().pathByAddingChild(sourceNode);
	}
	
	/**
	 * Returns an array of all the tree model listeners registered 
	 * on this component. 
	 * @return all of the component's TreeModelListener or an empty 
	 * array if no ancestor listeners are currently registered.
	 */
	public TreeModelListener[] getTreeModelListeners() {
		return treeModelListener.getListeners(TreeModelListener.class);
	}
	
	private void fireNodesChanged(TreeModelEvent event) {
		TreeModelListener[] listeners = getTreeModelListeners();
		for (int i = 0; i < listeners.length ; i++ ) {
			listeners[i].treeNodesChanged(event);
		}
	}
	
	private void fireNodesInserted(TreeModelEvent event) {
		TreeModelListener[] listeners = getTreeModelListeners();
		for (int i = 0; i < listeners.length ; i++ ) {
			listeners[i].treeNodesInserted(event);
		}
	}
	
	private void fireNodesRemoved(TreeModelEvent event) {
		TreeModelListener[] listeners = getTreeModelListeners();
		for (int i = 0; i < listeners.length ; i++ ) {
			listeners[i].treeNodesRemoved(event);
		}
	}
	
	private void fireStructureChanged(TreeModelEvent event) {
		TreeModelListener[] listeners = getTreeModelListeners();
		for (int i = 0; i < listeners.length ; i++ ) {
			listeners[i].treeStructureChanged(event);
		}
	}

	@Override
	public Object getChild(Object parent, int index) {
		if(parent instanceof TreeNode) {
			return ((TreeNode) parent).getChildAt(index);
		}
		return null;
	}

	@Override
	public int getChildCount(Object parent) {
		if(parent instanceof TreeNode) {
			return ((TreeNode) parent).getChildCount();
		}
		return 0;
	}

	@Override
	public int getIndexOfChild(Object parent, Object child) {
		if(parent instanceof TreeNode && child instanceof TreeNode) {
			return ((TreeNode) parent).getIndex((TreeNode)child);
		}
		return -1;
	}

	@Override
	public DefaultTreeNode getRoot() {
		return root;
	}

	@Override
	public boolean isLeaf(Object node) {
		if(node instanceof TreeNode) {
			return ((TreeNode) node).isLeaf();
		}
		return true;
	}
	
	private final class AspectsRoot extends CatalogOrAspectsRoot
			implements AspectsRootTreeNode, TreeNode {
		private static final long serialVersionUID = 1399422647775656103L;		
		
		protected IdList<Aspect>[] aspectsData;
		
		private AspectsNode[] nodes;
		private boolean nodesCreated = false;
		private void createNodes() {
			if(nodesCreated)
				return;
			
			nodesCreated = true;
			
			if(aspectsData == null)
				return;
			
			nodes = new AspectsNode[aspectsData.length];
			for(int i = 0 ; i<aspectsData.length ; i++) {				
				Aspect[] aspects = 
						new Aspect[aspectsData[i].size()];
				aspectsData[i].toArray(aspects);
				
				nodes[i] = new AspectsNode();
				nodes[i].nameData = namesData[i];
				nodes[i].parentData = this;
				
				nodes[i].aspects = aspectsData[i];
			}
		}

		@SuppressWarnings("unchecked")
		@Override
		public void removeNode(int index) {
			if(index < 0)
				return;
			
			int count = getChildCount();
			
			if(count == 1) {
				aspectsData = null;
				nodes = null;
				return;
			}
			
			int offset = 0;
			AspectsNode[] newNodes = new AspectsNode[count-1];
			IdList<Aspect>[] newAspectsData = new IdList[count-1];
			String[] newNamesData = new String[count-1];
			for(int i = 0; i < nodes.length ; i++) {
				if(i == index) {
					offset++;
					continue;
				}
				
				newNodes[i-offset] = nodes[i];
				newAspectsData[i-offset] = aspectsData[i];
				newNamesData[i-offset] = namesData[i];
			}
			
			nodes = newNodes;
			aspectsData = newAspectsData;
			namesData = newNamesData;			
		}
		
		@Override
		public void moveNode(int index0, int index1) {
			if(index0 < 0 || index1 < 0 || index0 == index1 
					|| index0 >= getChildCount()
					|| index1 >= getChildCount())
				return;

			AspectsNode tempNode = nodes[index0];
			IdList<Aspect> tempAspects = aspectsData[index0];
			String tempName = namesData[index0];
			
			if(index1 < index0) {
				for(int i = index0; i > index1 ; i--) {
					nodes[i] = nodes[i-1];
					aspectsData[i] = aspectsData[i-1];
					namesData[i] = namesData[i-1];
				}
			} else {
				for(int i = index0; i < index1 ; i++) {
					nodes[i] = nodes[i+1];
					aspectsData[i] = aspectsData[i+1];
					namesData[i] = namesData[i+1];
				}
			}
			
			nodes[index1] = tempNode;
			aspectsData[index1] = tempAspects;
			namesData[index1] = tempName;
		}
		
		@SuppressWarnings("unchecked")
		public AspectsNode insertAspectsNode(IdList<Aspect> aspects, 
				String name, int index) {
			
			AspectsNode aspectsNode = new AspectsNode();
			aspectsNode.nameData = name;
			aspectsNode.parentData = this;
			
			aspectsNode.aspects = aspects;
			
			int count = getChildCount();
			
			if(index < 0 || index > count)
				index = count;
			
			int offset = 0;
			IdList<Aspect>[] newAspectsData = new IdList[count+1];
			String[] newNamesData = new String[count+1];
			AspectsNode[] newNodes = new AspectsNode[count+1];
			for(int i = 0; i < newNodes.length ; i++) {
				if(i == index) {
					offset++;
					newAspectsData[i] = aspects;
					newNamesData[i] = name;
					newNodes[i] = aspectsNode;
					continue;
				}
				
				newAspectsData[i] = aspectsData[i-offset];
				newNamesData[i] = namesData[i-offset];
				newNodes[i] = nodes[i-offset];
			}
			
			aspectsData = newAspectsData;
			namesData = newNamesData;
			nodes = newNodes;
			
			return aspectsNode;
		}

		@Override
		public Enumeration<DefaultTreeNode> children() {
			createNodes();
			return new TreeNodeEnumeration(nodes);
		}

		@Override
		public DefaultTreeNode getChildAt(int childIndex) {			
			createNodes();
			return nodes[childIndex];
		}

		@Override
		public int getChildCount() {
			createNodes();
			
			if(nodes == null)
				return 0;
			
			return nodes.length;
		}

		@Override
		public int getIndex(TreeNode node) {
			createNodes();
			
			if(nodes == null)
				return -1;
			
			for(int i = 0; i < nodes.length ; i++) {
				if(node.equals(nodes[i])) {
					return i;
				}
			}
			return -1;
		}

		@Override
		public IdList<Aspect>[] getAspectsList() {
			return aspectsData;
		}

		@Override
		public String[] getAspectsListNames() {
			return namesData;
		}
	}
	
	private final class CatalogRoot extends CatalogOrAspectsRoot
			implements CatalogRootTreeNode, DefaultTreeNode {
		private static final long serialVersionUID = -7281908631566302973L;
		
		protected Catalog[] catalogsData;
		
		private CatalogNode[] nodes;
		private boolean nodesCreated = false;
		private void createNodes() {
			if(nodesCreated)
				return;
			
			nodesCreated = true;
			
			if(catalogsData == null)
				return;
			
			nodes = new CatalogNode[catalogsData.length];
			for(int i = 0 ; i<catalogsData.length ; i++) {				
				Aspect[] aspects = 
						new Aspect[catalogsData[i].getAspects().size()];
				catalogsData[i].getAspects().toArray(aspects);
				
				nodes[i] = new CatalogNode();
				nodes[i].nameData = namesData[i];
				nodes[i].parentData = this;
				
				nodes[i].catalog = catalogsData[i];
			}
		}
		
		@Override
		public void removeNode(int index) {
			if(index < 0)
				return;
			
			int count = getChildCount();
			
			if(count == 1) {
				catalogsData = null;
				nodes = null;
				return;
			}
			
			int offset = 0;
			CatalogNode[] newNodes = new CatalogNode[count-1];
			Catalog[] newCatalogsData = new Catalog[count-1];
			String[] newNamesData = new String[count-1];
			for(int i = 0; i < nodes.length ; i++) {
				if(i == index) {
					offset++;
					continue;
				}
				
				newNodes[i-offset] = nodes[i];
				newCatalogsData[i-offset] = catalogsData[i];
				newNamesData[i-offset] = namesData[i];
			}
			
			nodes = newNodes;
			catalogsData = newCatalogsData;
			namesData = newNamesData;
		}
		
		public CatalogNode insertCatalogNode(Catalog catalog, String name, 
				int index) {			
			CatalogNode catalogNode = new CatalogNode();
			catalogNode.nameData = name;
			catalogNode.parentData = this;
			
			catalogNode.catalog = catalog;
			
			int count = getChildCount();

			if(index < 0 || index > count)
				index = count;
			
			int offset = 0;
			Catalog[] newCatalogsData = new Catalog[count+1];
			String[] newNamesData = new String[count+1];
			CatalogNode[] newNodes = new CatalogNode[count+1];
			for(int i = 0; i < newNodes.length ; i++) {
				if(i == index) {
					offset++;
					newCatalogsData[i] = catalog;
					newNamesData[i] = name;
					newNodes[i] = catalogNode;
					continue;
				}
				
				newCatalogsData[i] = catalogsData[i-offset];
				newNamesData[i] = namesData[i-offset];
				newNodes[i] = nodes[i-offset];
			}
			
			catalogsData = newCatalogsData;
			namesData = newNamesData;
			nodes = newNodes;
			
			return catalogNode;
		}
		
		@Override
		public void moveNode(int index0, int index1) {
			if(index0 < 0 || index1 < 0 || index0 == index1 
					|| index0 >= getChildCount()
					|| index1 >= getChildCount())
				return;

			CatalogNode tempNode = nodes[index0];
			Catalog tempCatalog = catalogsData[index0];
			String tempName = namesData[index0];
			
			if(index1 < index0) {
				for(int i = index0; i > index1 ; i--) {
					nodes[i] = nodes[i-1];
					catalogsData[i] = catalogsData[i-1];
					namesData[i] = namesData[i-1];
				}
			} else {
				for(int i = index0; i < index1 ; i++) {
					nodes[i] = nodes[i+1];
					catalogsData[i] = catalogsData[i+1];
					namesData[i] = namesData[i+1];
				}
			}
			
			nodes[index1] = tempNode;
			catalogsData[index1] = tempCatalog;
			namesData[index1] = tempName;
		}

		@Override
		public Enumeration<DefaultTreeNode> children() {
			createNodes();
			return new TreeNodeEnumeration(nodes);
		}

		@Override
		public DefaultTreeNode getChildAt(int childIndex) {			
			createNodes();
			return nodes[childIndex];
		}

		@Override
		public int getChildCount() {
			createNodes();
			
			if(nodes == null)
				return 0;
			
			return nodes.length;
		}

		@Override
		public int getIndex(TreeNode node) {
			createNodes();
			
			if(nodes == null)
				return -1;
			
			if(nodes != null) {
				for(int i = 0; i < nodes.length ; i++) {
					if(node.equals(nodes[i])) {
						return i;
					}
				}
			}
			return -1;
		}

		@Override
		public String[] getCatalogNames() {
			return namesData;
		}

		@Override
		public Catalog[] getCatalogs() {
			return catalogsData;
		}
	}
	
	private abstract class CatalogOrAspectsRoot extends Node {
		private static final long serialVersionUID = 4541598024662096809L;
		protected String nameData;
		
		protected String[] namesData;
		
		public abstract void removeNode(int index);
		
		@Override
		public boolean getAllowsChildren() {
			return true;
		}
		
		@Override
		public DefaultTreeNode getParent() {
			return null;
		}

		@Override
		public boolean isLeaf() {
			return false;
		}
		
		public String getName() {
			return nameData;
		}
		
		@Override
		public String toString() {
			return getName();
		}
	}
	
	private final class AspectNode extends Node implements AspectTreeNode {
		private static final long serialVersionUID = -1207345927237767503L;
		
		Catalog catalog;
		IdList<Aspect> aspects;
		
		protected Aspect aspectData;
		protected DefaultTreeNode parentData;
		
		@Override
		public Enumeration<DefaultTreeNode> children() {
			return new TreeNodeEnumeration();
		}
		@Override
		public boolean getAllowsChildren() {
			return false;
		}
		@Override
		public DefaultTreeNode getChildAt(int childIndex) {
			return null;
		}
		@Override
		public int getChildCount() {
			return 0;
		}
		@Override
		public int getIndex(TreeNode node) {
			return -1;
		}
		@Override
		public DefaultTreeNode getParent() {
			return parentData;
		}
		@Override
		public boolean isLeaf() {
			return true;
		}
		@Override
		public String toString() {
			return getAspect().getDirective();
		}
		@Override
		public Aspect getAspect() {
			return aspectData;
		}
		@Override
		public void moveNode(int index0, int index1) {
		}		
	}
	
	private final class CategoryNode extends Node implements CategoryTreeNode {
		private static final long serialVersionUID = 8845016671512299082L;
		
		Catalog catalog;
		IdList<Aspect> aspects;
		
		protected String nameData;
		protected DefaultTreeNode parentData;
		
		private AspectNode[] nodes = null;
		private boolean nodesCreated = false;
		
		protected void generateAspectListOfNodes(List<Aspect> lst) {
			int count = getChildCount();
			
			AspectNode an;
			Aspect aspect;
			for(int i = 0 ; i < count ; i++) {
				an = getChildAt(i);
				aspect = an.getAspect();
				if(aspect.getDescription() == null)
					aspect.setDescription("");
				aspect.setCategory(getName());
				
				lst.add(aspect);
			}
		}
		
		private void createNodes() {
			if(nodesCreated)
				return;
			
			nodesCreated = true;
			
			Aspect[] aspectsData = null;
			
			if(catalog != null) {
				aspectsData = new Aspect[catalog.getAspects().size()];
				catalog.getAspects().toArray(aspectsData);
			} else if(aspects != null) {
				aspectsData = new Aspect[aspects.size()];
				aspects.toArray(aspectsData);
			} else
				return;
			
			aspectsData = Aspects.getCategoryAspects(aspectsData, nameData);
			
			if(aspectsData == null || aspectsData.length == 0)
				return;
			
			nodes = new AspectNode[aspectsData.length];
			for(int i = 0 ; i<aspectsData.length ; i++) {
				nodes[i] = new AspectNode();
				nodes[i].aspectData = aspectsData[i];
				nodes[i].parentData = this;
				
				nodes[i].aspects = aspects;
				nodes[i].catalog = catalog;
			}
		}
		
		public void removeNode(int index) {
			if(index < 0)
				return;
			
			int count = getChildCount();
			
			if(count == 1) {
				nodes = null;
				return;
			}
			
			int offset = 0;
			AspectNode[] newNodes = new AspectNode[count-1];
			for(int i = 0; i < nodes.length ; i++) {
				if(i == index) {
					offset++;
					continue;
				}
				
				newNodes[i-offset] = nodes[i];
			}
			
			nodes = newNodes;
		}
		
		public AspectNode insertAspectNode(Aspect aspect, int index) {
			aspect.setCategory(nameData);
			
			AspectNode aspectNode = new AspectNode();
			aspectNode.aspectData = aspect;
			aspectNode.parentData = this;
			
			aspectNode.catalog = catalog;
			aspectNode.aspects = aspects;
			
			int count = getChildCount();

			if(index < 0 || index > count)
				index = count;
			
			int offset = 0;
			AspectNode[] newNodes = new AspectNode[count+1];
			for(int i = 0; i < newNodes.length ; i++) {
				if(i == index) {
					offset++;
					newNodes[i] = aspectNode;
					continue;
				}
				
				newNodes[i] = nodes[i-offset];
			}
			
			nodes = newNodes;
			
			return aspectNode;
		}
		
		@Override
		public void moveNode(int index0, int index1) {
			if(index0 < 0 || index1 < 0 || index0 == index1 
					|| index0 >= getChildCount()
					|| index1 >= getChildCount())
				return;
			
			AspectNode tempNode = nodes[index0];
			
			if(index1 < index0) {
				for(int i = index0; i > index1 ; i--) {
					nodes[i] = nodes[i-1];
				}
			} else {
				for(int i = index0; i < index1 ; i++) {
					nodes[i] = nodes[i+1];
				}
			}
			
			nodes[index1] = tempNode;
		}
		
		@Override
		public String getName() {
			return nameData;
		}
		@Override
		public Enumeration<DefaultTreeNode> children() {
			createNodes();
			return new TreeNodeEnumeration(nodes);
		}
		@Override
		public boolean getAllowsChildren() {
			return true;
		}
		@Override
		public AspectNode getChildAt(int childIndex) {
			createNodes();
			return nodes[childIndex];
		}
		@Override
		public int getChildCount() {
			createNodes();
			if(nodes == null)
				return 0;
			return nodes.length;
		}
		@Override
		public int getIndex(TreeNode node) {
			createNodes();
			
			if(nodes == null)
				return -1;
			
			for(int i = 0; i < nodes.length ; i++) {
				if(node.equals(nodes[i])) {
					return i;
				}
			}
			return -1;
		}
		@Override
		public DefaultTreeNode getParent() {
			return parentData;
		}
		@Override
		public boolean isLeaf() {
			return false;
		}		
		@Override
		public String toString() {
			return getName();
		}		
	}
	
	private final class AspectsNode extends CatalogOrAspectsNode 
			implements AspectsTreeNode  {
		private static final long serialVersionUID = -6390699304814122555L;
		
		AspectsNode() {
			nameData = "Aspects Root";
		}
		
		@Override
		public String toString() {
			return getName();
		}

		@Override
		public IdList<Aspect> getAspects() {
			return aspects;
		}
	}
	
	private final class CatalogNode extends CatalogOrAspectsNode 
			implements CatalogTreeNode {
		private static final long serialVersionUID = -6390699304814122555L;
		
		@Override
		public String toString() {
			return getName();
		}

		@Override
		public Catalog getCatalog() {
			return catalog;
		}
	}
	
	private abstract class CatalogOrAspectsNode extends Node {
		private static final long serialVersionUID = 1921747427671854365L;
		
		Catalog catalog;
		IdList<Aspect> aspects;
		
		protected String nameData;
		protected DefaultTreeNode parentData;
		
		private AspectNode[] uncategorizedNodes = null;
		private CategoryNode[] categoryNodes = null;
		private boolean nodesCreated = false;
		
		protected void generateAspectListOfNodes(List<Aspect> lst) {
			int count = getChildCount();
			
			DefaultTreeNode tn;
			for(int i = 0 ; i < count ; i++) {
				tn = getChildAt(i);
				
				if(tn instanceof CategoryNode) {
					CategoryNode cn = (CategoryNode)tn;
					cn.generateAspectListOfNodes(lst);
				} else if(tn instanceof AspectNode ) {
					AspectNode an = (AspectNode)tn;
					Aspect aspect = an.getAspect();
					if(aspect.getDescription() == null)
						aspect.setDescription("");
					aspect.setCategory("");
					lst.add(aspect);
				}
			}
		}
		
		public void removeAllChildren() {
			uncategorizedNodes = null;
			categoryNodes = null;
			
			nodesCreated = true;
			
			if(catalog != null) {
				catalog.getAspects().clear();
			} else if(aspects != null) {
				aspects.clear();
			}
		}
		
		private void createNodes() {
			if(nodesCreated)
				return;
			
			nodesCreated = true;
			
			IdList<Aspect> aspectsData = null;
			
			if(catalog != null) {
				aspectsData = catalog.getAspects();
			} else if(aspects != null) {
				aspectsData = aspects;
			}
			
			if(aspectsData == null)
				return;
			
			/* uncategorized Nodes */			
			Aspect[] uncategorizedAspects = 
					Aspects.getCategoryAspects(aspectsData, null);
			if(uncategorizedAspects != null && uncategorizedAspects.length >0){
				uncategorizedNodes = 
						new AspectNode[uncategorizedAspects.length];
				for(int i = 0 ; i < uncategorizedAspects.length ; i++) {
					uncategorizedNodes[i] = new AspectNode();
					uncategorizedNodes[i].aspectData = uncategorizedAspects[i];
					uncategorizedNodes[i].parentData = this;
					
					uncategorizedNodes[i].catalog = catalog;
					uncategorizedNodes[i].aspects = aspects;
				}
			} else {
				uncategorizedNodes = null;
			}
			
			/* categorized nodes */
			String categories[] = Aspects.getCategories(aspectsData, false);
			if(categories != null) {
				categoryNodes = new CategoryNode[categories.length];
				for(int iCat = 0 ; iCat < categories.length ; iCat++) {
					String category = categories[iCat];

					categoryNodes[iCat] = new CategoryNode();
					categoryNodes[iCat].nameData = category;
					categoryNodes[iCat].parentData = this;

					categoryNodes[iCat].aspects = aspects;
					categoryNodes[iCat].catalog = catalog;
				}
			} else {
				categoryNodes = null;
			}
		}
		
		public void removeNode(int index) {
			if(index < 0)
				return;
			
			int count = getChildCount();
			
			if(count == 1) {
				categoryNodes = null;
				uncategorizedNodes = null;
				return;
			}
			
			if(categoryNodes != null && index < categoryNodes.length) {
				
				/* remove from category nodes */
				int offset = 0;
				CategoryNode[] newCategoryNodes = 
						new CategoryNode[categoryNodes.length-1];
				for(int i = 0; i < categoryNodes.length ; i++) {
					if(i == index) {
						offset++;
						continue;
					}
					
					newCategoryNodes[i-offset] = categoryNodes[i];
				}				
				categoryNodes = newCategoryNodes;
				
			} else if(uncategorizedNodes != null) {
				if(categoryNodes != null)
					index -= categoryNodes.length;
				
				/* remove from uncategorized nodes */
				int offset = 0;
				AspectNode[] newUncategorizedNodes = 
						new AspectNode[uncategorizedNodes.length-1];
				for(int i = 0; i < uncategorizedNodes.length ; i++) {
					if(i == index) {
						offset++;
						continue;
					}
					
					newUncategorizedNodes[i-offset] = uncategorizedNodes[i];
				}				
				uncategorizedNodes = newUncategorizedNodes;
			}
		}
		
		public AspectNode insertAspectNode(Aspect aspect, int index) {
			aspect.setCategory("");
			
			AspectNode aspectNode = new AspectNode();
			aspectNode.aspectData = aspect;
			aspectNode.parentData = this;
			
			aspectNode.catalog = catalog;
			aspectNode.aspects = aspects;
			
			createNodes();
			
			int count = 0;
			
			if(uncategorizedNodes != null)
				count = uncategorizedNodes.length;
			
			if(categoryNodes != null) 
				index -= categoryNodes.length;

			if(index < 0 || index > count)
				index = count;
			
			int offset = 0;
			AspectNode[] newNodes = new AspectNode[count+1];
			for(int i = 0; i < newNodes.length ; i++) {
				if(i == index) {
					offset++;
					newNodes[i] = aspectNode;
					continue;
				}
				
				newNodes[i] = uncategorizedNodes[i-offset];
			}
			
			uncategorizedNodes = newNodes;
			
			return aspectNode;
		}
		
		public CategoryNode insertCategoryNode(String category, int index) {
			CategoryNode categoryNode = new CategoryNode();
			categoryNode.nameData = category;
			categoryNode.parentData = this;
			
			categoryNode.aspects = aspects;
			categoryNode.catalog = catalog;
			
			createNodes();
			
			int count = 0;
			
			if(categoryNodes != null)
				count = categoryNodes.length;

			if(index < 0 || index > count)
				index = count;
			
			int offset = 0;
			CategoryNode[] newNodes = new CategoryNode[count+1];
			for(int i = 0; i < newNodes.length ; i++) {
				if(i == index) {
					offset++;
					newNodes[i] = categoryNode;
					continue;
				}
				
				newNodes[i] = categoryNodes[i-offset];
			}
			
			categoryNodes = newNodes;
			
			return categoryNode;
		}
		
		@Override
		public void moveNode(int index0, int index1) {
			
			if(index0 >= getChildCount() || index1 >= getChildCount())
				return;
			
			if(index0 <0 || index1<0)
				return;
			
			if(index0 == index1)
				return;
			
			int max = Math.max(index0, index1);
			
			if(categoryNodes != null && max < categoryNodes.length) {
				/*
				CategoryNode tempNode = categoryNodes[index0];
				categoryNodes[index0] = categoryNodes[index1];
				categoryNodes[index1] = tempNode;
				*/
				
				CategoryNode tempNode = categoryNodes[index0];
				
				if(index1 < index0) {
					for(int i = index0; i > index1 ; i--) {
						categoryNodes[i] = categoryNodes[i-1];
					}
				} else {
					for(int i = index0; i < index1 ; i++) {
						categoryNodes[i] = categoryNodes[i+1];
					}
				}
				
				categoryNodes[index1] = tempNode;
			} else if(uncategorizedNodes != null) {
				if(categoryNodes != null) {
					max -= categoryNodes.length;
					index0 -= categoryNodes.length;
					index1 -= categoryNodes.length;
				}
				if(max < uncategorizedNodes.length && max > 0) {
					/*
					AspectNode tempNode = uncategorizedNodes[index0];
					uncategorizedNodes[index0] = uncategorizedNodes[index1];
					uncategorizedNodes[index1] = tempNode;
					*/
					AspectNode tempNode = uncategorizedNodes[index0];
					
					if(index1 < index0) {
						for(int i = index0; i > index1 ; i--) {
							uncategorizedNodes[i] = uncategorizedNodes[i-1];
						}
					} else {
						for(int i = index0; i < index1 ; i++) {
							uncategorizedNodes[i] = uncategorizedNodes[i+1];
						}
					}
					
					uncategorizedNodes[index1] = tempNode;
				}
			}
		}
		
		@Override
		public Enumeration<DefaultTreeNode> children() {
			DefaultTreeNode[] a = new DefaultTreeNode[getChildCount()];
			
			if(categoryNodes != null) {
				for(int i = 0 ; i < categoryNodes.length ; i++){
					a[i] = categoryNodes[i];
				}
			}
			
			if(uncategorizedNodes != null) {
				int offset = 0;
				if(categoryNodes != null)
					offset = categoryNodes.length;
				for(int i = 0 ; i < uncategorizedNodes.length ; i++){
					a[i+offset] = uncategorizedNodes[i];
				}
			}
			
			return new TreeNodeEnumeration(a);
		}
		@Override
		public boolean getAllowsChildren() {
			return true;
		}
		@Override
		public DefaultTreeNode getChildAt(int childIndex) {
			createNodes();
			if(categoryNodes != null && childIndex < categoryNodes.length) {
				return categoryNodes[childIndex];
			} else {
				if(categoryNodes == null) {
					return uncategorizedNodes[childIndex];
				} else {
					int index = childIndex - categoryNodes.length;
					return uncategorizedNodes[index];
				}
			}
		}
		@Override
		public int getChildCount() {
			createNodes();
			return getCategoryChildCount() + getAspectChildCount();
		}
		public int getCategoryChildCount() {
			createNodes();
			if(categoryNodes == null)
				return 0;
			return categoryNodes.length;
		}
		public int getAspectChildCount() {
			createNodes();
			if(uncategorizedNodes == null)
				return 0;
			return uncategorizedNodes.length;
		}
		@Override
		public int getIndex(TreeNode node) {
			createNodes();
			int index = -1;
			/* category nodes */
			if(categoryNodes != null) {
				for(int i = 0; i < categoryNodes.length ; i++) {
					if(node.equals(categoryNodes[i])) {
						index = i;
						break;
					}
				}
			}
			
			/* aspect nodes */
			if(uncategorizedNodes != null && index < 0) {
				for(int i = 0; i < uncategorizedNodes.length ; i++) {
					if(node.equals(uncategorizedNodes[i])) {
						if(categoryNodes == null) {
							index = i;
						} else {
							index = i + categoryNodes.length;
						}
						break;
					}
				}
			}
			
			return index;
		}
		@Override
		public DefaultTreeNode getParent() {
			return parentData;
		}
		@Override
		public boolean isLeaf() {
			return false;
		}
		
		public String getName() {
			return nameData;
		}
	}
	
	private abstract class Node implements DefaultTreeNode, Serializable {
		private static final long serialVersionUID = 4462276367745982566L;
		private Object customObject;
		
		@Override
		public Object getCustomObject() {
			return customObject;
		}
		
		public void setCustomObject(Object object) {
			customObject = object;
		}
		
		public abstract void moveNode(int index0, int index1);
	}
	
	private class TreeNodeEnumeration implements Enumeration<DefaultTreeNode>, 
			Serializable {
		private static final long serialVersionUID = 2027399770899128792L;
		
		private DefaultTreeNode[] nodes = null;
		private int pos = 0;
		
		public TreeNodeEnumeration() {
		}

		public TreeNodeEnumeration(DefaultTreeNode[] nodes) {
			this();
			this.nodes = nodes;
			this.pos = 0;
		}
		
		@Override
		public boolean hasMoreElements() {
			if(nodes == null || pos >= nodes.length) {
				return false;
			} else {
				return true;
			}
		}

		@Override
		public DefaultTreeNode nextElement() {
			DefaultTreeNode node = null;
			
			if(hasMoreElements()) {
				node = nodes[pos];
				pos++;
			}
			
			return node;
		}		
	}
}